package com.example.study.config.redis;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.util.ReflectionUtils;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
 * @author fly_roushan
 * 2023/5/10 15:35
 */
//@Configuration
//@EnableCaching(proxyTargetClass = true)
public class SpringCacheConfig extends CachingConfigurerSupport {

    @Resource
    private ApplicationContext applicationContext;

    /**
     * SpringCache默认设置为有锁写入
     * 可根据cacheName自定义过期时间
     * cacheName = "cacheName#60"
     * #后为值，单位为每秒
     *
     * @param redisConnectionFactory factory
     * @return cacheManager
     */
    @Bean
    public CacheManager shortCacheManager(RedisConnectionFactory redisConnectionFactory) {
        return new CustomizerRedisCacheManager(RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory), RedisCacheConfiguration.defaultCacheConfig(), initTypeMap());
    }

    /**
     * 指定固定key，不要依赖生成器
     *
     * @return key generator
     */
    @Override
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> "";
    }

    // 获取使用@Cacheable的方法信息
    private Map<String, CacheInfo> initTypeMap() {
        Map<String, CacheInfo> cacheConfigMap = new HashMap<>(32);
        Arrays.stream(applicationContext.getBeanNamesForType(Object.class))
                .map(applicationContext::getType).filter(Objects::nonNull)
                .forEach(clazz -> ReflectionUtils.doWithMethods(clazz, method -> {
                            ReflectionUtils.makeAccessible(method);
                            Cacheable cacheable = AnnotationUtils.findAnnotation(method, Cacheable.class);
                            if (Objects.nonNull(cacheable)) {
                                for (String cache : cacheable.cacheNames()) {
                                    cacheConfigMap.put(cache, new CacheInfo(method.getGenericReturnType(), cacheable));
                                }
                            }
                        })
                );
        return cacheConfigMap;
    }
}
